package org.wso2.carbon.registry.lifecycle.accenture.sample;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.util.XMLUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.scxml.io.SCXMLParser;
import org.apache.commons.scxml.model.*;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.w3c.dom.Element;
import org.wso2.carbon.governance.registry.extensions.beans.CheckItemBean;
import org.wso2.carbon.governance.registry.extensions.beans.CustomCodeBean;
import org.wso2.carbon.governance.registry.extensions.beans.PermissionsBean;
import org.wso2.carbon.governance.registry.extensions.beans.ScriptBean;
import org.wso2.carbon.governance.registry.extensions.interfaces.CustomValidations;
import org.wso2.carbon.governance.registry.extensions.interfaces.Execution;
import org.wso2.carbon.mashup.javascript.hostobjects.registry.CollectionHostObject;
import org.wso2.carbon.mashup.javascript.hostobjects.registry.RegistryHostObject;
import org.wso2.carbon.mashup.javascript.hostobjects.registry.ResourceHostObject;
import org.wso2.carbon.mashup.utils.MashupConstants;
import org.wso2.carbon.registry.core.ActionConstants;
import org.wso2.carbon.registry.core.Aspect;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.handlers.RequestContext;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.registry.eventing.events.ResourceUpdatedEvent;
import org.wso2.carbon.registry.lifecycle.accenture.sample.utils.CustomEmailMessageUtil;
import org.wso2.carbon.registry.lifecycle.accenture.sample.utils.NotificationUtil;
import org.wso2.carbon.registry.lifecycle.accenture.sample.utils.SubscriptionUtil;
import org.wso2.carbon.registry.lifecycle.accenture.sample.utils.Utils;
import org.wso2.carbon.user.core.UserStoreException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.CharArrayReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class ServiceRequestLifeCycle extends Aspect {
    private static final Log log = LogFactory.getLog(ServiceRequestLifeCycle.class);

    public static final String REGISTRY_NOTIFICATION_PATH = "/_system/config/notifications/";
    private static final String REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_OPTION =
            "registry.custom_lifecycle.checklist.option.";
    private static final String REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_JS_SCRIPT_CONSOLE =
            "registry.custom_lifecycle.checklist.js.script.console.";
    private static final String REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_TRANSITION_UI =
            "registry.custom_lifecycle.checklist.transition.ui.";
    private static final String EXECUTION = "execution";
    private static final String VALIDATION = "validation";

    private String stateProperty = "registry.lifecycle.SoftwareProjectLifecycle.state";
    private String lifecycleProperty = "registry.LC.name";

    private List<String> states;
    private Map<String, List<CheckItemBean>> checkListItems;
    private Map<String, List<CustomCodeBean>> transitionValidations;
    private Map<String, List<CustomCodeBean>> transitionExecution;
    private Map<String, List<PermissionsBean>> transitionPermission;
    private Map<String, List<String>> stateEvents;
    private Map<String, List<ScriptBean>> scriptElements;
    private Map<String, Map<String, String>> transitionUIs;

    /**
     * This email subscription contains a list of roles (SOAManager, Consumer etc) for each action like
     * (promote. demote etc) in each state (NewRequest, DesignStarted etc)
     * This object in initialized from following element in Life-cycle configuration.
     *      <data name="subscriptions">
                <subscription type="email" eventName="Promote"
                      location="/_system/config/notifications/DesignStarted" roles="SOAArchitect" />
            </data>

     * eg -
     * NewRequest -> Promote -> Role1
     *            |          |
     *            |          -> Role2
     *            |
     *            -> Deomote -> Role3
     *            |
     *            -> Role4
     */
    private Map<String, Map<String, List<String>>> emailSubstriptions;


    boolean isConfigurationFromResource;
    private String configurationResourcePath;
    private OMElement configurationElement;
    private String aspectName;

    private Resource resource;
    private SCXML scxml;

    public ServiceRequestLifeCycle(OMElement config) {
        initialize();

        String currentAspectName = config.getAttributeValue(new QName("name"));
        aspectName = currentAspectName;
        currentAspectName = currentAspectName.replaceAll("\\s", "");
        stateProperty = "registry.lifecycle." + currentAspectName + ".state";

        Iterator stateElements = config.getChildElements();
        while (stateElements.hasNext()) {
            OMElement stateEl = (OMElement) stateElements.next();

            if (stateEl.getAttribute(new QName("type")) != null) {
                String type = stateEl.getAttributeValue(new QName("type"));
                if (type.equalsIgnoreCase("resource")) {
                    isConfigurationFromResource = true;
                    configurationResourcePath = RegistryUtils.getAbsolutePath(
                            RegistryContext.getBaseInstance(), stateEl.getText());
                    clearAll();
                    break;
                } else if (type.equalsIgnoreCase("literal")) {
                    isConfigurationFromResource = false;
                    configurationElement = stateEl.getFirstElement();
                    clearAll();
                    break;
                }
            }
            String name = stateEl.getAttributeValue(new QName("name"));
            if (name == null) {
                throw new IllegalArgumentException(
                        "Must have a name attribute for each state");
            }
            states.add(name);
        }
    }

    private void initialize() {
        states = new ArrayList<String>();
        checkListItems = new HashMap<String, List<CheckItemBean>>();
        transitionValidations = new HashMap<String, List<CustomCodeBean>>();
        transitionExecution = new HashMap<String, List<CustomCodeBean>>();
        transitionPermission = new HashMap<String, List<PermissionsBean>>();
        stateEvents = new HashMap<String, List<String>>();
        scriptElements = new HashMap<String, List<ScriptBean>>();
        transitionUIs = new HashMap<String, Map<String, String>>();
        emailSubstriptions = new HashMap<String, Map<String, List<String>>>();
    }

    private void clearAll() {
        states.clear();
        checkListItems.clear();
        transitionPermission.clear();
        transitionValidations.clear();
        transitionExecution.clear();
        transitionUIs.clear();
        emailSubstriptions.clear();
    }

    private void AddCheckItems(Resource resource, List<CheckItemBean> currentStateCheckItems, String state)
            throws UserStoreException {

        String[] roles = CurrentSession.getUserRealm().getUserStoreManager()
                .getRoleListOfUser(CurrentSession.getUser());
        if (currentStateCheckItems != null && roles.length > 0) {
            int order = 0;
            for (CheckItemBean currentStateCheckItem : currentStateCheckItems) {
                List<PermissionsBean> permissions = currentStateCheckItem.getPermissionsBeans();

                List<String> items = new ArrayList<String>();
                items.add("status:" + state);
                items.add("name:" + currentStateCheckItem.getName());
                items.add("value:false");
                items.add("order:" + order);
                if (getActionPermission(roles, permissions) || permissions == null) {
                    items.add("visible:true");
                } else {
                    items.add("visible:false");
                }
                String resourcePropertyNameForItem = REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_OPTION +
                                                     ++order + ".item";
                resource.setProperty(resourcePropertyNameForItem, items);

            }
        }

    }

    public Resource getResource() {
        return this.resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void associate(Resource resource, Registry registry)
            throws RegistryException {
        clearAll();

        try {
            setResource(resource);
            setScxmlConfiguration(registry);

            List<String> propertyValues = resource.getPropertyValues(lifecycleProperty);
            if (propertyValues != null && propertyValues.size() > 0) {
                return;
            }
            if (states.size() == 0) {
                try {
                    populateItems(registry);
                } catch (Exception e) {
                    log.error("ErrOR:" + e.getLocalizedMessage(), e);
                }
            }

            //Creating the checklist
            //this is the first time the life cycle is associated with a resource.
            String initialState = scxml.getInitial();
            AddCheckItems(resource, checkListItems.get(initialState), initialState);
            addScripts(initialState, resource);
            addTransitionUI(resource, initialState);

        } catch (XMLStreamException e) {
            log.error(e);
        } catch (SAXException e) {
            log.error(e);
        } catch (ModelException e) {
            log.error(e);
        } catch (IOException e) {
            log.error(e);
        } catch (Exception e) {
            throw new RegistryException("Resource does not contain a valid XML configuration: " + e.toString());
        }

        if(scxml.getInitial() != null) {
            resource.setProperty(stateProperty, scxml.getInitial().replace(".", " "));
        } else {
            log.error("Please check the initial state name defined is valid.");
        }
        resource.setProperty(lifecycleProperty, aspectName);
    }

    private void addTransitionUI(Resource resource, String stateName) {
        Map<String, String> currentStateTransitionUI = transitionUIs.get(stateName);

        if (currentStateTransitionUI != null) {
            for (Map.Entry<String, String> entry : currentStateTransitionUI.entrySet()) {
                resource.setProperty(REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_TRANSITION_UI + entry.getKey()
                        , entry.getValue());
            }
        } else {
            List<String> tobeRemoved = new ArrayList<String>();
            Properties properties = resource.getProperties();
            for (Object key : properties.keySet()) {
                if (key.toString().startsWith(REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_TRANSITION_UI)) {
                    tobeRemoved.add(key.toString());
                }
            }
            for (String key : tobeRemoved) {
                resource.removeProperty(key);
            }
        }
    }

    private void addScripts(String state, Resource resource) {
        List<ScriptBean> scriptList = scriptElements.get(state);
        if (scriptList != null) {
            for (ScriptBean scriptBean : scriptList) {
                if (scriptBean.isConsole()) {
                    List<String> items = new ArrayList<String>();
                    items.add(scriptBean.getScript());
                    items.add(scriptBean.getFunctionName());

                    String resourcePropertyNameForScript = REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_JS_SCRIPT_CONSOLE
                                                           + state + "." + scriptBean.getEventName();
                    resource.setProperty(resourcePropertyNameForScript, items);
                }
            }
        }
    }

    /**
     * This method responsible for creating object which represent the permission for a specific Event
     * See the configuration in life-cycle configuration below.
     * <permission forEvent="Promote" roles="SOAArchitect"/>
     *
     * @param permChild
     * @return
     */
    private PermissionsBean createPermissionBean(OMElement permChild) {
        PermissionsBean permBean = new PermissionsBean();
        permBean.setForEvent(permChild.getAttributeValue(new QName("forEvent")));
        if (permChild.getAttributeValue(new QName("roles")) != null) {
            permBean.setRoles(Arrays.asList(permChild.getAttributeValue(new QName("roles"))
                                                    .split(",")));
        }
        return permBean;
    }

    /**
     * Loads the custom code validation logic.
     * Custom code validator can be used to enforce some validation logic before some state change
     * happen in the life cycle configuration
     *
     * @param className
     * @param parameterMap
     * @return
     */
    private CustomValidations loadCustomValidators(String className, Map<String, String> parameterMap) {

        CustomValidations customValidations = null;
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Class<?> customCodeClass = Class.forName(className, true, loader);
            customValidations = (CustomValidations) customCodeClass.newInstance();
            customValidations.init(parameterMap);

        } catch (ClassNotFoundException e) {
            log.error("Unable to load validations class", e);
        } catch (InstantiationException e) {
            log.error("Unable to load validations class", e);
        } catch (IllegalAccessException e) {
            log.error("Unable to load validations class", e);
        }
        return customValidations;
    }

    /**
     * This method responsible for populating objects related to custom code executors or validator.
     * See for a sample custom validator and custom executor configurations below.
     * <validations>
     * <validation forEvent="" class="">
     * <!--forEvent - defines the event that the custom validation should be done
     * class - defines the full qualified name of the custom validations class -->
     * <parameter name="" value=""/>
     * <!--name - defines the custom parameter name
     * value - defines the custom parameter value-->
     * </validation>
     * </validations>
     * <p/>
     * <execution forEvent="Promote" class="">
     * <parameter name="" value=""/>
     * </execution>
     *
     * @param customCodeChild
     * @param type
     * @return
     */
    private CustomCodeBean createCustomCodeBean(OMElement customCodeChild, String type) {
        CustomCodeBean customCodeBean = new CustomCodeBean();
        Map<String, String> paramNameValues = new HashMap<String, String>();

        Iterator parameters = customCodeChild.getChildElements();
        while (parameters.hasNext()) {
            // this loop is for the parameter name and values
            OMElement paramChild = (OMElement) parameters.next();
            paramNameValues.put(paramChild.getAttributeValue(new QName("name")),
                                paramChild.getAttributeValue(new QName("value")));
        }
        if (type.equals(VALIDATION)) {
            customCodeBean.setClassObeject(loadCustomValidators(
                    customCodeChild.getAttributeValue(new QName("class")), paramNameValues));
        } else if (type.equals(EXECUTION)) {
            customCodeBean.setClassObeject(loadCustomExecutors(
                    customCodeChild.getAttributeValue(new QName("class")), paramNameValues));
        }
        customCodeBean.setEventName(customCodeChild.getAttributeValue(new QName("forEvent")));
        return customCodeBean;
    }


    private Execution loadCustomExecutors(String className, Map<String, String> parameterMap) {

        Execution customExecutors = null;
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Class<?> customCodeClass = Class.forName(className, true, loader);
            customExecutors = (Execution) customCodeClass.newInstance();
            customExecutors.init(parameterMap);

        } catch (ClassNotFoundException e) {
            log.error("Unable to load validations class " + className, e);
        } catch (InstantiationException e) {
            log.error("Unable to load validations class " + className, e);
        } catch (IllegalAccessException e) {
            log.error("Unable to load validations class " + className, e);
        }
        return customExecutors;
    }


    /**
     * This is where the children in the scxml (life-cycle configuration) get populated and build
     * the internal object model like states, checklist items, subscriptions etc.
     *
     * @param registry
     * @throws Exception
     */
    private void populateItems(Registry registry) throws Exception {
        Map stateList = scxml.getChildren();

        for (Object stateObject : stateList.entrySet()) {

            Map.Entry state = (Map.Entry) stateObject;

            String currentStateName = (String) state.getKey();
            State currentState = (State) state.getValue();
            Datamodel model = currentState.getDatamodel();

            states.add(currentStateName);
            if (model != null) {
                List dataList = model.getData();
                for (Object dataObject : dataList) {
                    Data data = (Data) dataObject;
                    OMElement node = XMLUtils.toOM((Element) data.getNode());

                    //when associating we will map the custom data model to a set of beans.
                    //These will be used for further actions.adding the checkItems
                    populateCheckListItems(currentStateName, node);

                    populateStateValidations(currentStateName, node);

                    populateEmailSubscriptions(currentStateName, node, registry);

                    populateTransitionPermissions(currentStateName, node);

                    populateScriptElements(currentStateName, node);

                    populateTransitionUI(currentStateName, node);

                    populateTransitionExecution(currentStateName, node);
                }
            }

            List<String> events = new ArrayList<String>();
            for (Object t : currentState.getTransitionsList()) {
                Transition transition = (Transition) t;
                events.add(transition.getEvent());
            }
            stateEvents.put(currentStateName, events);
        }
    }

    private void populateCheckListItems(String currentStateName, OMElement node) {
        if (!checkListItems.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("checkItems"))) {

            List<CheckItemBean> checkItems = new ArrayList<CheckItemBean>();

            Iterator checkItemIterator = node.getChildElements();
            while (checkItemIterator.hasNext()) {
                CheckItemBean checkItemBean = new CheckItemBean();
                OMElement childElement = (OMElement) checkItemIterator.next();

                //setting the check item name
                checkItemBean.setName(childElement.getAttributeValue(new QName("name")));

                //setting the transactionList
                if ((childElement.getAttributeValue(new QName("forEvent"))) != null) {
                    checkItemBean.setEvents(Arrays.asList((childElement
                                                                   .getAttributeValue(new QName("forEvent"))).split(",")));
                }

                Iterator permissionElementIterator = childElement
                        .getChildrenWithName(new QName("permissions"));

                while (permissionElementIterator.hasNext()) {
                    OMElement permissionElement = (OMElement) permissionElementIterator.next();

                    Iterator permissions = permissionElement.getChildElements();
                    List<PermissionsBean> permBeanList = new ArrayList<PermissionsBean>();

                    while (permissions.hasNext()) {
                        OMElement permChild = (OMElement) permissions.next();
                        permBeanList.add(createPermissionBean(permChild));
                    }
                    checkItemBean.setPermissionsBeans(permBeanList);
                }

                Iterator validationsElementIterator = childElement
                        .getChildrenWithName(new QName("validations"));

                while (validationsElementIterator.hasNext()) {
                    //setting the validation bean
                    List<CustomCodeBean> customCodeBeanList = new ArrayList<CustomCodeBean>();
                    OMElement validationElement = (OMElement) validationsElementIterator.next();
                    Iterator validations = validationElement.getChildElements();

                    //this loop is to iterate the validation elements
                    while (validations.hasNext()) {
                        OMElement validationChild = (OMElement) validations.next();
                        customCodeBeanList.add(createCustomCodeBean(validationChild, VALIDATION));
                    }
                    checkItemBean.setValidationBeans(customCodeBeanList);
                }
                checkItems.add(checkItemBean);
            }
            if (checkItems.size() > 0) {
                checkListItems.put(currentStateName, checkItems);
            }
        }
    }

    private void populateTransitionExecution(String currentStateName, OMElement node) {
        if (!transitionExecution.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("transitionExecution"))) {
            List<CustomCodeBean> customCodeBeanList = new ArrayList<CustomCodeBean>();
            Iterator executorsIterator = node.getChildElements();
            while (executorsIterator.hasNext()) {
                OMElement executorChild = (OMElement) executorsIterator.next();
                customCodeBeanList.add(createCustomCodeBean(executorChild, EXECUTION));
            }
            transitionExecution.put(currentStateName, customCodeBeanList);
        }
    }

    //Adding the transition UIs
    private void populateTransitionUI(String currentStateName, OMElement node) {
        if (!transitionUIs.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("transitionUI"))) {
            Map<String, String> uiEventMap = new HashMap<String, String>();
            Iterator uiIterator = node.getChildElements();

            while (uiIterator.hasNext()) {
                OMElement uiElement = (OMElement) uiIterator.next();
                uiEventMap.put(uiElement.getAttributeValue(new QName("forEvent"))
                        , uiElement.getAttributeValue(new QName("href")));
            }
            transitionUIs.put(currentStateName, uiEventMap);
        }
    }

    //Adding the script elements
    private void populateScriptElements(String currentStateName, OMElement node) {
        if (!scriptElements.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("transitionScripts"))) {
            List<ScriptBean> scriptBeans = new ArrayList<ScriptBean>();
            Iterator scriptIterator = node.getChildElements();

            while (scriptIterator.hasNext()) {
                OMElement script = (OMElement) scriptIterator.next();
                Iterator scriptChildIterator = script.getChildElements();
                while (scriptChildIterator.hasNext()) {
                    OMElement scriptChild = (OMElement) scriptChildIterator.next();
                    scriptBeans.add(new ScriptBean(scriptChild.getQName().getLocalPart().equals("console"),
                                                   scriptChild.getAttributeValue(new QName("function")),
                                                   script.getAttributeValue(new QName("forEvent")),
                                                   scriptChild.getFirstElement().toString()));
                }
            }
            scriptElements.put(currentStateName, scriptBeans);
        }
    }

    //Adding the transition permissions
    private void populateTransitionPermissions(String currentStateName, OMElement node) {
        if (!transitionPermission.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("transitionPermission"))) {
            List<PermissionsBean> permissionsBeanList = new ArrayList<PermissionsBean>();
            Iterator permissionIterator = node.getChildElements();
            while (permissionIterator.hasNext()) {
                OMElement permChild = (OMElement) permissionIterator.next();
                permissionsBeanList.add(createPermissionBean(permChild));
            }
            transitionPermission.put(currentStateName, permissionsBeanList);
        }

    }

    //Adding the state validations
    private void populateStateValidations(String currentStateName, OMElement node) {
        if (!transitionValidations.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("transitionValidation"))) {
            List<CustomCodeBean> customCodeBeanList = new ArrayList<CustomCodeBean>();
            Iterator validationsIterator = node.getChildElements();
            while (validationsIterator.hasNext()) {
                OMElement validationChild = (OMElement) validationsIterator.next();
                customCodeBeanList.add(createCustomCodeBean(validationChild, VALIDATION));
            }
            transitionValidations.put(currentStateName, customCodeBeanList);
        }
    }

    private String generateCustomSubscriptionLocationFromState(String state, String service) {
        return Utils.getSubscriptionPath(state, service);
        //return REGISTRY_NOTIFICATION_PATH + state + "_" + getResource().getAuthorUserName();
    }

    /**
     * This method will be used to generate the subscription path from Life cycle config
     *
     * @param subscriptionLocationFromLCConfig
     *
     * @return
     */
    private String generateCustomSubscriptionLocationFromLocation(String subscriptionLocationFromLCConfig) throws RegistryException {
        //Creating the custom subscription location based on the current resource
        String[] array = subscriptionLocationFromLCConfig.split("/");
        String state = array[array.length - 1];

        try {
            if (getResource().getContent() != null) {
                return generateCustomSubscriptionLocationFromState(state, Utils.getServiceNameFromResource(getResource()));
            } else {
                String msg = "content is null for resource:" + getResource().getPath();
                log.error(msg);
                throw new RegistryException(msg);
            }

        } catch (RegistryException e) {
            String msg = "Service Name generation from resource:" + getResource().getPath() + " failed.";
            log.error(msg, e);
            throw new RegistryException(msg, e);
        }
    }

    //Adding the subscriptions
    private void populateEmailSubscriptions(String currentStateName, OMElement node, Registry registry) {

        //Adding the subscriptions
        /*if (!emailSubstriptions.containsKey(currentStateName)
            && (node.getAttributeValue(new QName("name")).equals("subscriptions"))) {
            Map<String, String> subscriptionMap = new HashMap<String, String>();
            Iterator subscriptionsIterator = node.getChildElements();
            while (subscriptionsIterator.hasNext()) {
                OMElement subscription = (OMElement) subscriptionsIterator.next();

                if ((subscription != null) && ((subscription.getAttribute(new QName("type"))).getAttributeValue().equals("email"))) {
                    String eventName = subscription.getAttributeValue(new QName("eventName"));

                    try {
                        String destinationAddress = generateCustomSubscriptionLocationFromLocation(subscription.getAttributeValue(new QName("location")));
                        subscriptionMap.put(eventName, destinationAddress);
                    } catch (Exception e) {
                        log.error("Subscription path failed for event:" + eventName, e);
                    }

                    //TODO: Verify this
                    //Here we create all the subscription paths.
                    ////createSubscriptionResource(registry, destinationAddress);
                    //Utils.createSubscriptionResource(registry, destinationAddress);


                }
            }
            emailSubstriptions.put(currentStateName, subscriptionMap);
        }*/

        if (!emailSubstriptions.containsKey(currentStateName) &&
            (node.getAttributeValue(new QName("name")).equals("subscriptions"))) {

            Map<String, List<String>> actionToRolesMap = new HashMap<String, List<String>>();
            for (Iterator<OMElement> subscriptionsIterator = node.getChildElements(); subscriptionsIterator.hasNext();) {
                OMElement subscription = subscriptionsIterator.next();

                if ((subscription != null) && ((subscription.getAttribute(new QName("type"))).getAttributeValue().equals("email"))) {
                    String eventName = subscription.getAttributeValue(new QName("eventName"));
                    String roleString = subscription.getAttributeValue(new QName("roles"));

                    if (!roleString.equals("") && roleString != null) {
                        String[] roleArray = roleString.split(",");
                        /*for (String role : roleArray) {
                            role = role.trim();
                        }*/
                        for (int i = 0; i < roleArray.length; i++) {
                            roleArray[i] = roleArray[i].trim();
                        }

                        actionToRolesMap.put(eventName, Arrays.asList(roleArray));
                    } else {
                        log.warn("Roles attribute is not defined for the subscriptions. So no subscriptions " +
                                 "declared for state:" + currentStateName);
                    }
                }
            }
            emailSubstriptions.put(currentStateName, actionToRolesMap);
        }
    }

    private void setScxmlConfiguration(Registry registry) throws RegistryException, XMLStreamException, IOException,
                                                                 SAXException, ModelException {
        /*String xmlContent;
        if (isConfigurationFromResource) {
            Resource configurationResource = registry.get(configurationResourcePath);
            xmlContent = new String((byte[]) configurationResource.getContent());
            configurationElement = AXIOMUtil.stringToOM(xmlContent);
        }

        OMElement scxmlElement = configurationElement.getFirstElement();
        scxml = SCXMLParser.parse(new InputSource(new CharArrayReader((scxmlElement.toString()).toCharArray())), null);*/

        String xmlContent;
        if (isConfigurationFromResource) {
            Resource configurationResource = registry.get(configurationResourcePath);
            xmlContent = new String((byte[]) configurationResource.getContent());
            try {
                configurationElement = AXIOMUtil.stringToOM(xmlContent);
            } catch (XMLStreamException e) {
                e.printStackTrace();
            }
        }

        OMElement scxmlElement = configurationElement.getFirstElement();
        InputSource src = new InputSource(new CharArrayReader((scxmlElement.toString()).toCharArray()));
        try {
            scxml = SCXMLParser.parse(src, null);
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void dissociate(RequestContext arg0) {
        log.info("dissociate method called");
        // TODO Auto-generated method stub

    }

    @Override
    public void invoke(RequestContext context, String action) throws RegistryException {
        invoke(context, action, Collections.<String, String>emptyMap());
    }

    @Override
    public void invoke(RequestContext requestContext, String action, Map<String, String> parameterMap)
            throws RegistryException {
        Resource resource = requestContext.getResource();
        String currentState = resource.getProperty(stateProperty).replace(" ", ".");
        String resourcePath = requestContext.getResourcePath().getPath();
        String newResourcePath;
        String nextState = currentState;

        State currentExecutionState = (State) scxml.getChildren().get(currentState);

        //Modify here for the checkitem and other validations.
        List transitions = currentExecutionState.getTransitionsList();
        try {
            String user = CurrentSession.getUser();
            String[] roles = CurrentSession.getUserRealm().getUserStoreManager().getRoleListOfUser(user);

            List<String> possibleEvents = getPossibleActions(resource, currentState);
            if (possibleEvents.size() > 0) {
                for (Object o : transitions) {
                    String eventName = ((Transition) o).getEvent();
                    if (possibleEvents.contains(eventName) && eventName.contains(action)) {
                        try {
                            //transition validations go here
                            //There is need to check the transition permissions again as well to avoid fraud
                            if (getTransitionPermission(roles, transitionPermission.get(currentState), eventName)) {
                                if (doAllCustomValidations(requestContext, currentState)) {
                                    //The transition happens here.
                                    nextState = ((Transition) o).getNext();
                                    //We have identified the next state here
                                    //We are going to run the custom executors now
                                    runCustomExecutorsCode(action, requestContext, transitionExecution.get(currentState)
                                            , currentState, nextState);
                                    //Doing the JS execution
                                    List<ScriptBean> scriptElement = scriptElements.get(currentState);
                                    if (scriptElement != null) {
                                        for (ScriptBean scriptBean : scriptElement) {
                                            if (scriptBean.getEventName().equals(eventName) && !scriptBean.isConsole()) {
                                                executeJS(AXIOMUtil.stringToOM(scriptBean.getScript()).getText()
                                                          + "\n" + scriptBean.getFunctionName() + "()");
                                            }
                                        }
                                    }
                                    break;
                                } else {
                                    log.info("Transition validations failed.");
                                    throw new RegistryException("Transition validations failed.");
                                }
                            }
                        } catch (XMLStreamException e) {
                            log.error("JavaScript execution failed :", e);
                        }
                    }
                }
            }
        } catch (UserStoreException e) {
            log.error("Failed to get the current user role :" + e.toString());
        }
        // We are getting the resource again because the users can change its properties from the executors
        if (requestContext.getResource() == null) {
            requestContext.setResource(resource);
            requestContext.setProcessingComplete(true);
            return;
        }
        resource = requestContext.getResource();
        newResourcePath = requestContext.getResourcePath().getPath();

        if (!currentState.equals(nextState)) {
            State state = (State) scxml.getChildren().get(nextState);
            resource.setProperty(stateProperty, state.getId().replace(".", " "));
            try {
                AddCheckItems(resource, checkListItems.get(state.getId()), state.getId());
                addScripts(state.getId(), resource);
                addTransitionUI(resource, state.getId());
            } catch (UserStoreException e) {
                log.error("Authentication exception", e);
            }
        }
//        if (action != null && action.equals("Promote")) {
            requestContext.getRegistry().put(newResourcePath, resource);
//        }

        if ("itemClick".equals(action)) {
            // We are not sending notifications for itemClick.
            return;
        }

        //Notification code
        notifyEmailSubscriptions(requestContext, action, currentState, nextState);

    }

    private void notifyEmailSubscriptions(RequestContext requestContext, String action, String currentState, String nextState) throws RegistryException {
        //New notification code
        if (emailSubstriptions.get(currentState) != null) {
            String msg = null;

            if (action != null) {
                //msg = requestContext.getResourcePath().getPath() + "  " + action + "d to \"" + nextState + ".\" from \"" + currentState + "\".";

                //get service name to construct the email message
                String serviceName = Utils.getServiceNameFromResource(requestContext.getResource());
                //get backEnd server URL to construct the resource URL
                String backEndURL = NotificationUtil.getBackendServerUrl();
                /*msg = "New Service Request for " + serviceName + "was created and available at path \n" +
                             backEndURL + "carbon/resources/resource.jsp?region=region3&item=resource_browser_menu&path="
                             + requestContext.getResourcePath().getPath() + " \n";  */
                String servicePath = backEndURL + "carbon/resources/resource.jsp?region=region3&item=resource_browser_menu&path="
                             + requestContext.getResourcePath().getPath();
                try {
                    msg = CustomEmailMessageUtil.generateMessageFromStateAndAction(currentState, action, serviceName, servicePath);
                } catch (Exception e) {
                    msg = "There's an error occurred during the message generation of email!.";
                }
            }

            //String subscriptionPath = Utils.getSubscriptionPath(parameterMap.get("Email"));

            //Retrieving the emails for the the roles defined in emailSubstriptions
            List<String> roleList = emailSubstriptions.get(currentState).get(action);

            SubscriptionUtil subUtil = new SubscriptionUtil();
            subUtil.initSubscriptionMap(requestContext);

            //This can be null when roles are not defined in the roles in the Life-cycle configuration.
            if (roleList != null) {
                List<String> emailList = subUtil.getEmailsForGivenRoles(roleList);

                Utils.setDefaultNotificationServiceURL(requestContext.getSystemRegistry().getEventingServiceURL(null));
                UserRegistry reg = (UserRegistry) requestContext.getSystemRegistry();

                for (String email : emailList) {
                    String subscriptionPath = Utils.getSubscriptionPath(email);
                    ResourceUpdatedEvent<String> resourceUpdatedEvent = new ResourceUpdatedEvent<String>(msg);
                    resourceUpdatedEvent.setResourcePath(subscriptionPath);
                    resourceUpdatedEvent.setTenantId(reg.getTenantId());

                    try {
                        NotificationUtil.notify(resourceUpdatedEvent, requestContext.getSystemRegistry(), null);
                    } catch (Exception e) {
                        log.error("Notification sent failed", e);
                    }
                }
            }
        } else {
            log.error("No subscriptions are configured for the state: " + currentState + "in ServiceRequestLC.");
        }
    }

    /**
     * This method used to execute the JavaScripts <server> elements defined in life cycle configuration.
     *
     * @param script that need to be execute at server side
     */
    private void executeJS(String script) {
        Context cx = Context.getCurrentContext();
        try {
            ConfigurationContext configurationContext =
                    MessageContext.getCurrentMessageContext().getConfigurationContext();
            cx.putThreadLocal(MashupConstants.AXIS2_CONFIGURATION_CONTEXT, configurationContext);
            AxisService service = new AxisService();
            service.addParameter(MashupConstants.MASHUP_AUTHOR, CurrentSession.getUser());
            cx.putThreadLocal(MashupConstants.AXIS2_SERVICE, service);
            Scriptable scope = cx.initStandardObjects();
            ScriptableObject.defineClass(scope, ResourceHostObject.class);
            ScriptableObject.defineClass(scope, CollectionHostObject.class);
            ScriptableObject.defineClass(scope, RegistryHostObject.class);
            Object result = cx.evaluateString(scope, script, "<cmd>", 1, null);
            if (result != null && log.isInfoEnabled()) {
                log.info("JavaScript Result: " + Context.toString(result));
            }
        } catch (IllegalAccessException e) {
            log.error("Unable to defining registry host objects.", e);
        } catch (InstantiationException e) {
            log.error("Unable to instantiate the given registry host object.", e);
        } catch (InvocationTargetException e) {
            log.error("An exception occurred while creating registry host objects.", e);
        } catch (AxisFault e) {
            log.error("Failed to set user name parameter.", e);
        } catch (SecurityException ignored) {
            // If there is a security issue, simply live with that. This portion of the sample is
            // not intended to work on a system with security restrictions.
        } finally {
            Context.exit();
        }
    }

    private boolean runCustomExecutorsCode(String action, RequestContext context, List<CustomCodeBean> customCodeBeans
            , String currentState, String nextState)
            throws RegistryException {

        //This method is used to run the custom executors
        if (customCodeBeans != null) {
            for (CustomCodeBean customCodeBean : customCodeBeans) {
                if (customCodeBean.getEventName().equals(action)) {
                    Execution customExecutor = (Execution) customCodeBean.getClassObeject();
                    if (!customExecutor.execute(context, currentState, nextState)) {
                        throw new RegistryException("Execution failed for action : " + customCodeBean.getEventName());
                    }
                }
            }
        }
        return true;
    }

    private boolean doAllCustomValidations(RequestContext context, String currentState) throws RegistryException {
        //doing the check item validations
        List<CheckItemBean> currentStateCheckItems = checkListItems.get(currentState);
        if (currentStateCheckItems != null) {
            for (CheckItemBean currentStateCheckItem : currentStateCheckItems) {
                try {
                    runCustomValidationsCode(context, currentStateCheckItem.getValidationBeans());
                } catch (RegistryException registryException) {
                    throw new RegistryException("Validation failed for check item : "
                                                + currentStateCheckItem.getName());
                }
            }
        }
        //doing the transition validations
        return runCustomValidationsCode(context, transitionValidations.get(currentState));
    }

    private boolean runCustomValidationsCode(RequestContext context, List<CustomCodeBean> customCodeBeans)
            throws RegistryException {

        //This method is used to validate both checkitem validations and transition validations
        if (customCodeBeans != null) {
            for (CustomCodeBean customCodeBean : customCodeBeans) {
                CustomValidations customValidations = (CustomValidations) customCodeBean.getClassObeject();
                if (!customValidations.validate(context)) {
                    throw new RegistryException("Validation failed for action : " + customCodeBean.getEventName());
                }
            }
        }
        return true;
    }


    @Override
    public String[] getAvailableActions(RequestContext context) {

        Resource resource = context.getResource();
        String currentState = resource.getProperty(stateProperty).replace(" ", ".");

        try {
            if (states.size() == 0 || !states.contains(currentState)) {
                clearAll();
                Registry registry = context.getRegistry();
                setScxmlConfiguration(registry);
                populateItems(registry);
            }

        } catch (Exception e) {
            throw new RuntimeException("Resource does not contain a valid XML configuration: " + e.toString());
        }
        //Need to check whether the correct user has done the checking

        ArrayList<String> actions = new ArrayList<String>();
        String user = CurrentSession.getUser();

        State currentExecutionState = (State) (scxml.getChildren()).get(currentState);
        List currentTransitions = currentExecutionState.getTransitionsList();

        try {
            List<PermissionsBean> permissionsBeans = transitionPermission.get(currentState);
            String[] roles = CurrentSession.getUserRealm().getUserStoreManager().getRoleListOfUser(user);

            // In this loop we do both of the following tasks
            // 1.Make check items visible, not visible to the user
            // 2.Get the list of actions that is possible to the user

            for (Object currentTransition : currentTransitions) {
                Transition t = (Transition) currentTransition;
                String transitionName = t.getEvent();

                Properties properties = resource.getProperties();
                List<String> propertyNames = new LinkedList<String>();
                for (Object o : properties.keySet()) {
                    String temp = o.toString();
                    if (temp.startsWith(REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_OPTION)) {
                        propertyNames.add(temp);
                    }
                }
                for (String propertyName : propertyNames) {
                    //sets the visibility of all to false;
                    Set<String> propValueOld = new HashSet<String>(resource.getPropertyValues(propertyName));
                    propValueOld.remove("visible:true");
                    propValueOld.add("visible:false");

                    resource.removeProperty(propertyName);
                    resource.setProperty(propertyName, new ArrayList<String>(propValueOld));

                    String propertyValue = properties.get(propertyName).toString();
                    List<CheckItemBean> currentStateCheckItems = checkListItems.get(currentState);
                    if (currentStateCheckItems != null && roles.length > 0) {
                        for (CheckItemBean currentStateCheckItem : currentStateCheckItems) {
                            List<PermissionsBean> permissions = currentStateCheckItem.getPermissionsBeans();
                            if (permissions == null || getActionPermission(roles, permissions)) {
                                if (propertyValue.contains(currentStateCheckItem.getName())) {
                                    Set<String> propValue = new HashSet<String>(resource
                                                                                        .getPropertyValues(propertyName));
                                    propValue.remove("visible:false");
                                    propValue.add("visible:true");

                                    resource.removeProperty(propertyName);
                                    resource.setProperty(propertyName, new ArrayList<String>(propValue));
                                }
                            }
                        }
                    }
                }

                List<String> possibleActions = getPossibleActions(resource, currentState);
                if ((getTransitionPermission(roles, permissionsBeans, transitionName) || permissionsBeans == null)
                    && possibleActions.contains(transitionName)) {
                    actions.add(transitionName);
                }
            }
            if (CurrentSession.getUserRealm().getAuthorizationManager().isUserAuthorized(user, resource.getPath()
                    , ActionConstants.PUT)) {
                context.getRegistry().put(resource.getPath(), resource);
            }
        } catch (UserStoreException e) {
            log.error("Failed to get the current user role :", e);
        } catch (RegistryException e) {
            log.error("Failed to put the resource to the registry :", e);
        }

        log.info("ActionSize:" + actions.size());
        for (String action : actions) {
            log.info("Action:" + action);
        }
        return actions.toArray(new String[actions.size()]);
    }

    private boolean getActionPermission(String[] roles, List<PermissionsBean> permissionsBeans) {
        Set<String> permissionRoleSet = new HashSet<String>(Arrays.asList(roles));
        if (permissionsBeans != null) {
            for (PermissionsBean permission : permissionsBeans) {
                if (permission.getRoles() != null) {
                    List permRoles = permission.getRoles();
                    permissionRoleSet.retainAll(permRoles);
                }
            }
        }
        return !permissionRoleSet.isEmpty();
    }

    private List<String> getPossibleActions(Resource resource, String currentState) {

        Properties propertyNameValues = resource.getProperties();
        Iterator propIterator = propertyNameValues.entrySet().iterator();
        List<CheckItemBean> checkItems = checkListItems.get(currentState);
        List<String> events = new ArrayList<String>(stateEvents.get(currentState));

        if (checkItems != null && checkItems.size() > 0) {
            while (propIterator.hasNext()) {
                Map.Entry entry = (Map.Entry) propIterator.next();
                String propertyName = (String) entry.getKey();

                if (propertyName.startsWith(REGISTRY_CUSTOM_LIFECYCLE_CHECKLIST_OPTION)) {
                    List<String> propValues = (List<String>) entry.getValue();
                    for (String propValue : propValues) {
                        if (propValue.startsWith("name:")) {
                            for (CheckItemBean checkItem : checkItems) {
                                if ((checkItem.getName().equals(propValue.substring(propValue.indexOf(":") + 1))) &&
                                    (checkItem.getEvents() != null) && propValues.contains("value:false")) {
                                    events.removeAll(checkItem.getEvents());
                                }
                            }
                        }
                    }
                }

            }
        }
        return events;
    }

    private boolean getTransitionPermission(String[] roles, List<PermissionsBean> permissionsBeans, String eventName) {
        Set<String> premSet = new HashSet<String>(Arrays.asList(roles));
        if (permissionsBeans != null) {
            for (PermissionsBean permission : permissionsBeans) {
                if (permission.getForEvent().equals(eventName) && permission.getRoles() != null) {
                    List permRoles = permission.getRoles();
                    premSet.retainAll(permRoles);
                }
            }
        }
        return !premSet.isEmpty();
    }


}
